﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using RoketPack.Management;
using System.Diagnostics;
using System.IO;
using System.Threading;

namespace RoketPack
{
    public partial class MainForm : Form
    {
        private Manager m_Manager = null;

        internal MainForm(Manager manager)
        {
            // Initalize the components on the form.
            this.InitializeComponent();

            // Hide the debugging window.  It currently doesn't output anything
            // because we didn't really need it for this version of RoketPack.
            this.c_DebugLog.Visible = false;
            this.c_SoftwareSplitContainer.Panel2Collapsed = true;

            // Assign a variable to keep a reference to our manager.
            this.m_Manager = manager;

            // Set the correct form window icon.
            this.Icon = this.m_Manager.FormIcon;

            // Initalize the web browser.
            this.InitalizeWebBrowser();

            // Listen for the RepositoriesRefreshed event.
            this.m_Manager.RepositoriesRefreshed += new EventHandler(m_Manager_RepositoriesRefreshed);

            if (this.m_Manager.RepositoriesInitialized)
            {
                // Initalize the software list so that it contains all of the
                // available and installed applications.
                this.InitializeList();
            }
            else
            {
                // Disable all of the on-screen component and show a message
                // indicating a initialization is in progress.
                this.SetRepositoryInitializationPage();
                this.c_SoftwareListView.Items.Clear();
                this.c_SoftwareListView.Groups.Clear();
                this.c_SoftwareListView.Groups.Add(new ListViewGroup("reporefresh", "Please Wait..."));
                this.c_SoftwareListView.Items.Add(
                    new ListViewItem(
                        "Repository initialization is in progress...",
                        "roketpack",
                        this.c_SoftwareListView.Groups["reporefresh"]
                        )
                        );
                this.c_SoftwareListView.Enabled = false;
            }
        }

        /// <summary>
        /// Initalize the web browser.
        /// </summary>
        private void InitalizeWebBrowser()
        {
            this.c_DetailsWebBrowser.Navigate("about:blank");
            this.c_DetailsWebBrowser.Refresh();

            this.SetDefaultPage();
        }

        /// <summary>
        /// Initalize the software list view with the applications that are
        /// stored in the Manager.
        /// </summary>
        internal void InitializeList()
        {
            // Clear the existing list view of all items and groups.
            this.c_SoftwareListView.Items.Clear();
            this.c_SoftwareListView.Groups.Clear();
            this.c_Applications.Images.Clear();

            // Store a reference to all the groups we've created so we
            // can easily get a reference to a particular group later.
            Dictionary<string, ListViewGroup> groups = new Dictionary<string,ListViewGroup>();

            // Loop through all of the repositories and applications
            // to load the information into the list.
            foreach (Repository r in this.m_Manager.Repositories)
            {
                // Make sure the repository is correctly loaded.
                if (r.Applications == null)
                    return;

                foreach (RoketPack.Management.Application a in r.Applications.Values)
                {
                    // Detect / grab a reference to the group that is used
                    // by this application.
                    ListViewGroup group = null;
                    if (groups.Keys.Contains(a.Category))
                        group = groups[a.Category];
                    else
                    {
                        group = new ListViewGroup(a.Category, HorizontalAlignment.Left);
                        groups.Add(a.Category, group);
                        this.c_SoftwareListView.Groups.Add(group);
                    }

                    // Add the new icon to the c_Application image list.
                    if (a.Image != null)
                        this.c_Applications.Images.Add(a.Image);

                    // Create the new list item.
                    ListViewItem item = null;
                    if (a.Image != null)
                    {
                        item = new ListViewItem(
                             a.Name,
                             this.c_Applications.Images.Count - 1,
                             group);
                    }
                    else
                    {
                        item = new ListViewItem(
                             a.Name,
                             group);
                    }

                    // Setup the item for correct display as well as maintain
                    // a reference to the original application and repository
                    // instances.
                    item.UseItemStyleForSubItems = false;
                    item.Tag = new ListViewData(r, a);

                    // Add the subitem to indicate the current status of the
                    // application.
                    ListViewItem.ListViewSubItem subitem = new ListViewItem.ListViewSubItem(
                        item,
                        ""
                        );

                    if (a.IsInstalled())
                    {
                        this.UpdateListItem(a, subitem, Installation.InstallMode.NONE);
                    }
                    else if (!a.Available)
                    {
                        subitem.Text = "Not Available";
                        subitem.ForeColor = SystemColors.Menu;
                        item.ForeColor = SystemColors.Menu;
                    }

                    // Add the status subitem to the main item.
                    item.SubItems.Add(subitem);

                    // Add the main item to the list view.
                    this.c_SoftwareListView.Items.Add(item);
                }
            }
        }        

        /// <summary>
        /// The event function which is called when the user selects a new
        /// item in the list view.  We retrieve the data from the Tag property
        /// and use it to determine what page to show in the WebBrowser component.
        /// </summary>
        private void c_SoftwareListView_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (this.c_SoftwareListView.SelectedItems.Count == 0)
                return;

            RoketPack.Management.Application app = ((ListViewData)this.c_SoftwareListView.SelectedItems[0].Tag).Application;
            this.SetProductPage(app);
        }

        /// <summary>
        /// Sets the Web Browser to show the specified application page.
        /// </summary>
        /// <param name="app">The application</param>
        private void SetProductPage(RoketPack.Management.Application app)
        {
            HtmlDocument d = this.c_DetailsWebBrowser.Document;
            if (d != null)
            {
                d.OpenNew(true);
                d.Write(@"
<head>
<title>Application</title>
<link href='file://" + Environment.CurrentDirectory.Replace("\\", "/") + @"/stylesheet.css' rel='stylesheet' type='text/css'>
</head>
<body>
</body>");
                try
                {
                    d.Title = "Application - " + app.Name;
                    d.Body.InnerHtml = WebRender.GetProductHTML(app);
                }
                catch (NullReferenceException)
                {
                    d.Body.InnerHtml = WebRender.GetUnknownProductHTML();
                }
            }
        }

        /// <summary>
        /// Sets the Web Browser to show the unknown request page.
        /// </summary>
        private void SetUnknownRequestPage()
        {
            HtmlDocument d = this.c_DetailsWebBrowser.Document;
            if (d != null)
            {
                d.OpenNew(true);
                d.Write(@"
<head>
<title>Application</title>
<link href='file://" + Environment.CurrentDirectory.Replace("\\", "/") + @"/stylesheet.css' rel='stylesheet' type='text/css'>
</head>
<body>
</body>");
                d.Body.InnerHtml = WebRender.GetUnknownActionHTML();
            }
        }

        /// <summary>
        /// Sets the Web Browser to show the default welcome page.
        /// </summary>
        private void SetDefaultPage()
        {
            // Ensure that the function is called from the same thread
            // that the main form is on.
            if (this.c_DetailsWebBrowser.InvokeRequired)
            {
                RoketPack.Manager.VoidLambda lambda = () =>
                    {
                        this.SetDefaultPage();
                    };
                this.c_DetailsWebBrowser.Invoke(lambda);
                return;
            }

            HtmlDocument d = this.c_DetailsWebBrowser.Document;

            if (d != null)
            {
                d.OpenNew(true);
                d.Write(@"
<head>
<title>Application</title>
<link href='file://" + Environment.CurrentDirectory.Replace("\\", "/") + @"/stylesheet.css' rel='stylesheet' type='text/css'>
</head>
<body>
</body>");
                d.Body.InnerHtml = WebRender.GetDefaultPage();
            }
        }

        /// <summary>
        /// Sets the Web Browser to show the repository refresh in progress page.
        /// </summary>
        private void SetRepositoryRefreshPage()
        {
            HtmlDocument d = this.c_DetailsWebBrowser.Document;
            if (d != null)
            {
                d.OpenNew(true);
                d.Write(@"
<head>
<title>Application</title>
<link href='file://" + Environment.CurrentDirectory.Replace("\\", "/") + @"/stylesheet.css' rel='stylesheet' type='text/css'>
</head>
<body>
</body>");
                d.Body.InnerHtml = WebRender.GetRepositoryRefreshHTML();
            }
        }

        /// <summary>
        /// Sets the Web Browser to show the repository initialization in progress page.
        /// </summary>
        private void SetRepositoryInitializationPage()
        {
            HtmlDocument d = this.c_DetailsWebBrowser.Document;
            if (d != null)
            {
                d.OpenNew(true);
                d.Write(@"
<head>
<title>Application</title>
<link href='file://" + Environment.CurrentDirectory.Replace("\\", "/") + @"/stylesheet.css' rel='stylesheet' type='text/css'>
</head>
<body>
</body>");
                d.Body.InnerHtml = WebRender.GetRepositoryInitializationHTML();
            }
        }
        
        /// <summary>
        /// Simulates a navigation in the DetailsWebBrowser.  Used for handling
        /// the roket: protocol.
        /// 
        /// TODO: Move this protocol handling functionality into it's own class.
        /// </summary>
        /// <param name="url"></param>
        internal void HandleURLNavigation(string url, bool safe)
        {
            WebBrowserNavigatedEventArgs e = new WebBrowserNavigatedEventArgs(new Uri(url));
            SafeSender s = new SafeSender();
            s.IsSafe = safe;
            this.c_DetailsWebBrowser_Navigated(s, e);
        }

        private struct SafeSender
        {
            internal bool IsSafe;
        }

        /// <summary>
        /// The event function which is called when the user selects a link
        /// within the web browser itself.  Here we read the current URL and then
        /// perform actions based on the request.
        /// </summary>
        private void c_DetailsWebBrowser_Navigated(object sender, WebBrowserNavigatedEventArgs e)
        {
            // Detect whether this query is unsafe (i.e. it originated
            // via the API).
            bool safe = true;
            if (sender is SafeSender)
                safe = ((SafeSender)sender).IsSafe;

            // Parse the query component.            
            Dictionary<string, string> get = new Dictionary<string, string>();
            HttpStyleUriParser parser = new HttpStyleUriParser();
            foreach (string kv in e.Url.Query.TrimStart('?').Split('&'))
            {
                string[] p = kv.Split('=');
                if (p.Length == 2)
                    get.Add(p[0], p[1]);
            }
            
            // Define local variables for switch statement.
            string repository = null;
            string appid = null;
            string compid = null;
            bool handled = false;

            // Handle different requests.
            switch (e.Url.AbsolutePath)
            {
                case "add":
                    try
                    {
                        repository = get["repository"].TrimEnd('/');
                    }
                    catch (KeyNotFoundException)
                    {
                        Errors.Raise(Errors.ErrorType.ERROR_INVALID_URL_REQUEST);
                        return;
                    }
                    handled = false;

                    foreach (Repository r in this.m_Manager.Repositories)
                    {
                        if (r.Url != repository)
                            continue;

                        handled = true;
                        break;
                    }

                    if (!handled)
                    {
                        // No matching repository was found - prompt the user
                        // to install the repository.
                        RepositoryInstallationWizard repoinstall = new RepositoryInstallationWizard(this.m_Manager, repository);
                        repoinstall.TopMost = true;
                        repoinstall.ShowDialog();
                    }

                    break;
                case "install":
                    try
                    {
                        repository = get["repository"].TrimEnd('/');
                        appid = get["product"];
                        compid = get["component"];
                    }
                    catch (KeyNotFoundException)
                    {
                        Errors.Raise(Errors.ErrorType.ERROR_INVALID_URL_REQUEST);
                        return;
                    }
                    handled = false;

                    foreach (Repository r in this.m_Manager.Repositories)
                    {
                        if (r.Url != repository)
                            continue;

                        try
                        {
                            RoketPack.Management.Application a = r.Applications[appid];
                            RoketPack.Management.Component c = a.Components[compid];

                            // Check to see if the component is already installed...
                            if (c.IsInstalled())
                            {
                                if (this.Visible == false)
                                    this.m_Manager.SystemIconShowBalloon(100, a.Name, "The selected software is already installed.", ToolTipIcon.Warning);
                                return;
                            }

                            if (!safe)
                            {
                                // Make sure the user doesn't see the handling page while
                                // confirming the request.
                                this.SetProductPage(a);
                                this.c_DetailsWebBrowser.Update();

                                // Prompt the user before installation occurs.
                                ConfirmationForm confirm = new ConfirmationForm(r.Name, a.Name, c.Name, false);
                                if (confirm.ShowDialog() != DialogResult.Yes)
                                    return;
                            }

                            if (this.Visible == false)
                            {
                                this.m_Manager.SystemIconShowBalloon(100, a.Name, "The selected software is now being installed.", ToolTipIcon.Info);
                            }

                            // Start animating the system tray icon.
                            IconAnimator animator = this.m_Manager.GetIconAnimator(
                                IconAnimator.InstallSet,
                                this.m_Manager.SmallIcon,
                                100);

                            // Start the installation.
                            a.Install(c,
                                new Installation.InstallationProgressHandler(this.OnInstallProgress),
                                new Installation.InstallationCompleteHandler(this.OnInstallComplete),
                                new Installation.InstallationFailedHandler(this.OnInstallFailed),
                                animator
                                );

                            this.SetProductPage(a);
                            this.c_DetailsWebBrowser.Update();
                        }
                        catch (IndexOutOfRangeException)
                        {
                            this.SetProductPage(null);
                            this.c_DetailsWebBrowser.Update();
                        }

                        handled = true;

                        break;
                    }

                    if (!handled)
                    {
                        // No matching repository was found - prompt the user
                        // to install the repository.
                        RepositoryInstallationWizard repoinstall = new RepositoryInstallationWizard(this.m_Manager, repository);
                        repoinstall.TopMost = true;
                        repoinstall.ShowDialog();
                        if (repoinstall.InstalledRepository)
                        {
                            // Wait until the repository is successfully added...
                            EventHandler lambda = null;
                            int count = 0;
                            lambda = (lsender, le) =>
                                 {
                                     // There will be two events sent - after initalization
                                     // and after the actual refresh.  We want to trigger
                                     // on the second of those.
                                     if (count == 1)
                                     {
                                         this.m_Manager.RepositoriesRefreshed -= lambda;
                                         this.c_DetailsWebBrowser_Navigated(sender, e);
                                     }
                                     else
                                         count += 1;
                                 };
                            this.m_Manager.RepositoriesRefreshed += lambda;
                        }
                        else
                        {
                            this.SetProductPage(null);
                            this.c_DetailsWebBrowser.Update();
                        }
                    }

                    break;
                case "update":
                    try
                    {
                        repository = get["repository"].TrimEnd('/');
                        appid = get["product"];
                        compid = get["component"];
                    }
                    catch (KeyNotFoundException)
                    {
                        Errors.Raise(Errors.ErrorType.ERROR_INVALID_URL_REQUEST);
                        return;
                    }
                    handled = false;

                    foreach (Repository r in this.m_Manager.Repositories)
                    {
                        if (r.Url != repository)
                            continue;

                        try
                        {
                            RoketPack.Management.Application a = r.Applications[appid];
                            RoketPack.Management.Component c = a.Components[compid];

                            // Check to see if the component is already up-to-date...
                            if (!c.IsOutOfDate())
                            {
                                if (this.Visible == false)
                                    this.m_Manager.SystemIconShowBalloon(100, a.Name, "The selected software is up-to-date.", ToolTipIcon.Warning);
                                return;
                            }

                            if (!safe)
                            {
                                // It doesn't matter whether the update was done via
                                // the API - update requests aren't considered to be
                                // modifing what components are installed; they simply
                                // bring a component up-to-date, and hence the user
                                // does not need to be prompted (since it's most likely
                                // that an application is used the API to update itself).
                            }

                            // Start animating the system tray icon.
                            IconAnimator animator = this.m_Manager.GetIconAnimator(
                                IconAnimator.InstallSet,
                                this.m_Manager.SmallIcon,
                                100);

                            // Start the installation.
                            a.Update(c,
                                new Installation.InstallationProgressHandler(this.OnUpdateProgress),
                                new Installation.InstallationCompleteHandler(this.OnUpdateComplete),
                                new Installation.InstallationFailedHandler(this.OnUpdateFailed),
                                animator
                                );

                            this.SetProductPage(a);
                            this.c_DetailsWebBrowser.Update();
                        }
                        catch (IndexOutOfRangeException)
                        {
                            this.SetProductPage(null);
                            this.c_DetailsWebBrowser.Update();
                        }

                        handled = true;

                        break;
                    }

                    if (!handled)
                    {
                        // No matching repository was found - show the product
                        // not found page.
                        this.SetProductPage(null);
                        this.c_DetailsWebBrowser.Update();
                    }

                    break;
                case "uninstall":
                    try
                    {
                        repository = get["repository"].TrimEnd('/');
                        appid = get["product"];
                        compid = get["component"];
                    }
                    catch (KeyNotFoundException)
                    {
                        Errors.Raise(Errors.ErrorType.ERROR_INVALID_URL_REQUEST);
                        return;
                    }
                    handled = false;

                    foreach (Repository r in this.m_Manager.Repositories)
                    {
                        if (r.Url != repository)
                            continue;

                        try
                        {
                            RoketPack.Management.Application a = r.Applications[appid];
                            RoketPack.Management.Component c = a.Components[compid];

                            // Check to see if the component is not installed.
                            if (!c.IsInstalled())
                            {
                                if (this.Visible == false)
                                    this.m_Manager.SystemIconShowBalloon(100, a.Name, "The selected software is not installed.", ToolTipIcon.Warning);
                                return;
                            }

                            if (!safe || sender is WebBrowser)
                            {
                                // Make sure the user doesn't see the handling page while
                                // confirming the request.
                                this.SetProductPage(a);
                                this.c_DetailsWebBrowser.Update();

                                // Prompt the user before installation occurs.
                                ConfirmationForm confirm = new ConfirmationForm(r.Name, a.Name, c.Name, true);
                                if (confirm.ShowDialog() != DialogResult.Yes)
                                    return;
                            }

                            if (this.Visible == false)
                            {
                                this.m_Manager.SystemIconShowBalloon(100, a.Name, "The selected software is now being installed.", ToolTipIcon.Info);
                            }

                            // Start animating the system tray icon.
                            IconAnimator animator = this.m_Manager.GetIconAnimator(
                                IconAnimator.InstallSet,
                                this.m_Manager.SmallIcon,
                                100);

                            // Start the installation.
                            a.Uninstall(c,
                                new Installation.InstallationProgressHandler(this.OnUninstallProgress),
                                new Installation.InstallationCompleteHandler(this.OnUninstallComplete),
                                new Installation.InstallationFailedHandler(this.OnUninstallFailed),
                                animator
                                );

                            this.SetProductPage(a);
                            this.c_DetailsWebBrowser.Update();
                        }
                        catch (IndexOutOfRangeException)
                        {
                            this.SetProductPage(null);
                            this.c_DetailsWebBrowser.Update();
                        }

                        handled = true;

                        break;
                    }

                    if (!handled)
                    {
                        // No matching repository was found - show the product
                        // not found page.
                        this.SetProductPage(null);
                        this.c_DetailsWebBrowser.Update();
                    }

                    break;
                case "launch":
                    try
                    {
                        repository = get["repository"].TrimEnd('/');
                        appid = get["product"];
                    }
                    catch (KeyNotFoundException)
                    {
                        Errors.Raise(Errors.ErrorType.ERROR_INVALID_URL_REQUEST);
                        return;
                    }
                    handled = false;

                    foreach (Repository r in this.m_Manager.Repositories)
                    {
                        if (r.Url != repository)
                            continue;

                        try
                        {
                            RoketPack.Management.Application a = r.Applications[appid];

                            // Launch the application if possible.
                            if (!a.Launchable)
                            {
                                this.SetUnknownRequestPage();
                                this.c_DetailsWebBrowser.Update();
                            }
                            else
                            {
                                try
                                {
                                    // Commented out lines are reserved for debugging of
                                    // command-line applications.

                                    Process proc = new Process();
                                    proc.StartInfo.FileName = PathTranslator.Parse(a.LaunchPath);
                                    proc.StartInfo.WorkingDirectory = Path.GetDirectoryName(proc.StartInfo.FileName);
                                    proc.StartInfo.Arguments = PathTranslator.Parse(a.LaunchArgs);
                                    /*
                                     * proc.StartInfo.UseShellExecute = false;
                                     * proc.StartInfo.RedirectStandardOutput = true;
                                     * proc.StartInfo.RedirectStandardError = true;
                                     */
                                    proc.Start();

                                    /*
                                     * StreamReader reader = proc.StandardOutput;
                                     * StreamReader errorreader = proc.StandardError;
                                     * 
                                     * proc.WaitForExit();
                                     * string o = reader.ReadToEnd();
                                     * string eo = errorreader.ReadToEnd();
                                     * reader.Close();
                                     */

                                    this.m_Manager.IMManager.AnnounceApplicationExecution(a.Name);

                                    this.SetProductPage(a);
                                    this.c_DetailsWebBrowser.Update();
                                }
                                catch (InvalidOperationException ex)
                                {
                                    this.SetUnknownRequestPage();
                                    this.c_DetailsWebBrowser.Update();
                                }
                                catch (Win32Exception ex)
                                {
                                    this.SetUnknownRequestPage();
                                    this.c_DetailsWebBrowser.Update();
                                }
                            }
                        }
                        catch (IndexOutOfRangeException)
                        {
                            this.SetProductPage(null);
                            this.c_DetailsWebBrowser.Update();
                        }

                        handled = true;

                        break;
                    }

                    if (!handled)
                    {
                        // No matching repository was found - show the product
                        // not found page.
                        this.SetProductPage(null);
                        this.c_DetailsWebBrowser.Update();
                    }

                    break;
                case "blank":
                    // Do nothing - changing the page content here will 
                    // corrupt native memory.
                    break;
                default:
                    this.SetUnknownRequestPage();
                    this.c_DetailsWebBrowser.Update();
                    break;
            }
        }

        /// <summary>
        /// Updates the status of a list item depending on the data in the Application
        /// instance.
        /// </summary>
        /// <param name="application">The application to read the data from.</param>
        /// <param name="item">The item to update.</param>
        /// <param name="mode">The current mode of installation.</param>
        private void UpdateListItem(RoketPack.Management.Application application, ListViewItem.ListViewSubItem item, Installation.InstallMode mode)
        {
            int total = 0;
            int installed = 0;
            bool updates = false;
            foreach (RoketPack.Management.Component c in application.Components.Values)
            {
                total += 1;
                if (c.IsInstalled())
                {
                    installed += 1;
                    if (c.IsOutOfDate())
                        updates = true;
                }
            }
            if (updates)
            {
                item.Font = new Font(item.Font, FontStyle.Bold);
                item.ForeColor = Color.ForestGreen;
                item.Text = "Updates Available!";
            }
            else if (installed == 0 && mode == Installation.InstallMode.INSTALL)
            {
                // No components are detecting installation... but we just
                // installed one!  Signal installation failure.
                throw new ApplicationException("Installation of component was not detected by RoketPack.");
            }
            else if (installed == 0 && mode == Installation.InstallMode.UNINSTALL)
            {
                item.Text = "Uninstallation Complete";

                // Automatically clear the "Uninstallation Complete" message.
                ThreadStart autoclear = () =>
                    {
                        RoketPack.Manager.VoidLambda lambda = () =>
                            {
                                this.UpdateListItem(application, item, Installation.InstallMode.NONE);
                            };

                        Thread.Sleep(3000);

                        if (this.InvokeRequired)
                            this.Invoke(lambda);
                        else
                            lambda();
                    };
                Thread thread = new Thread(autoclear);
                thread.Name = "Uninstallation Status Autoclear";
                thread.IsBackground = true;
                thread.Start();
            }
            else if (total == installed || application.ID == Manager.RoketPackID && application.Repository.Url == Manager.OfficialRepository)
                item.Text = "Installed";
            else if (installed > 0)
                item.Text = "Installed (" + installed + "/" + total + " components)";
            else
                item.Text = "";
        }

        /// <summary>
        /// Callback executed when the installation of an application component is
        /// successful.
        /// </summary>
        /// <param name="sender">The sender of the event.</param>
        /// <param name="e">The event information.</param>
        private void OnInstallComplete(object sender, Installation.InstallationEventArgs e)
        {
            RoketPack.Management.Application a = e.Installation.Application;
            ListViewItem.ListViewSubItem item = this.GetSubItemForApplication(a);
            if (item == null) return;

            Manager.VoidLambda lambda = () =>
            {
                item.ForeColor = Color.Black;
                item.Font = new Font(item.Font, FontStyle.Regular);
                try
                {
                    this.UpdateListItem(a, item, e.Installation.Mode);
                }
                catch (ApplicationException)
                {
                    this.OnInstallFailed(sender, e);
                    return;
                }
                e.Installation.Application.InstallationData = null;
                this.SetProductPage(e.Installation.Application);

                if (this.Visible == false)
                {
                    this.m_Manager.SystemIconShowBalloon(100, a.Name, "Installation of " + a.Name + " is complete.", ToolTipIcon.Info);
                }

                e.Installation.Animator.Stop();
            };

            if (this.c_SoftwareListView.InvokeRequired)
                this.Invoke(lambda);
            else
                lambda();
        }

        /// <summary>
        /// Callback executed when the installation of an application component
        /// progresses further.
        /// </summary>
        /// <param name="sender">The sender of the event.</param>
        /// <param name="e">The event information.</param>
        private void OnInstallProgress(object sender, Installation.InstallationEventArgs e)
        {
            RoketPack.Management.Application a = e.Installation.Application;
            ListViewItem.ListViewSubItem item = this.GetSubItemForApplication(a);
            if (item == null) return;

            Manager.VoidLambda lambda = () =>
            {
                item.ForeColor = Color.ForestGreen;
                item.Font = new Font(item.Font, FontStyle.Bold);
                if (e.Installation.Stage == Installation.StageMode.DOWNLOAD)
                    item.Text = "Downloading... " + e.Installation.Progress + "% (file " + e.Installation.ProcessedFiles + " of " + e.Installation.TotalFiles + ")";
                else
                {
                    item.Text = "Installing... (file " + e.Installation.ProcessedFiles + " of " + e.Installation.TotalFiles + ")";
                    this.c_SoftwareListView.Update();
                }
            };

            if (this.c_SoftwareListView.InvokeRequired)
                this.Invoke(lambda);
            else
                lambda();
        }

        /// <summary>
        /// Callback executed when the installation of an application component
        /// has failed.
        /// </summary>
        /// <param name="sender">The sender of the event.</param>
        /// <param name="e">The event information.</param>
        private void OnInstallFailed(object sender, Installation.InstallationEventArgs e)
        {
            RoketPack.Management.Application a = e.Installation.Application;
            ListViewItem.ListViewSubItem item = this.GetSubItemForApplication(a);
            if (item == null) return;

            Manager.VoidLambda lambda = () =>
            {
                item.ForeColor = Color.OrangeRed;
                item.Font = new Font(item.Font, FontStyle.Bold);
                item.Text = "Installation Failed";
                e.Installation.Application.InstallationData = null;
                this.SetProductPage(e.Installation.Application);

                if (this.Visible == false)
                {
                    this.m_Manager.SystemIconShowBalloon(100, a.Name, "RoketPack was unable to install " + a.Name + ".", ToolTipIcon.Error);
                }

                e.Installation.Animator.Stop();
            };

            if (this.c_SoftwareListView.InvokeRequired)
                this.Invoke(lambda);
            else
                lambda();
        }

        /// <summary>
        /// Callback executed when the update of an application component is successful.
        /// </summary>
        /// <param name="sender">The sender of the event.</param>
        /// <param name="e">The event information.</param>
        private void OnUpdateComplete(object sender, Installation.InstallationEventArgs e)
        {
            if (e.Installation.Application.ID == Manager.RoketPackID &&
                e.Installation.Application.Repository.Url == Manager.OfficialRepository &&
                SelfUpdate.IsRequired())
            {
                // We need to restart RoketPack.
                SelfUpdate.Restart(this.m_Manager);
                return;
            }

            RoketPack.Management.Application a = e.Installation.Application;
            ListViewItem.ListViewSubItem item = this.GetSubItemForApplication(a);
            if (item == null) return;

            Manager.VoidLambda lambda = () =>
            {
                item.ForeColor = Color.Black;
                item.Font = new Font(item.Font, FontStyle.Regular);
                this.UpdateListItem(a, item, e.Installation.Mode);
                e.Installation.Application.InstallationData = null;
                this.SetProductPage(e.Installation.Application);

                if (this.Visible == false)
                {
                    this.m_Manager.SystemIconShowBalloon(100, a.Name, a.Name + " has successfully been updated.", ToolTipIcon.Info);
                }

                e.Installation.Animator.Stop();
            };

            if (this.c_SoftwareListView.InvokeRequired)
                this.Invoke(lambda);
            else
                lambda();
        }

        /// <summary>
        /// Callback executed when the update of an application component progresses
        /// further.
        /// </summary>
        /// <param name="sender">The sender of the event.</param>
        /// <param name="e">The event information.</param>
        private void OnUpdateProgress(object sender, Installation.InstallationEventArgs e)
        {
            RoketPack.Management.Application a = e.Installation.Application;
            ListViewItem.ListViewSubItem item = this.GetSubItemForApplication(a);
            if (item == null) return;

            Manager.VoidLambda lambda = () =>
            {
                item.ForeColor = Color.ForestGreen;
                item.Font = new Font(item.Font, FontStyle.Bold);
                if (e.Installation.Stage == Installation.StageMode.DOWNLOAD)
                    item.Text = "Downloading... " + e.Installation.Progress + "% (file " + e.Installation.ProcessedFiles + " of " + e.Installation.TotalFiles + ")";
                else
                {
                    item.Text = "Updating... (file " + e.Installation.ProcessedFiles + " of " + e.Installation.TotalFiles + ")";
                    this.c_SoftwareListView.Update();
                }
            };

            if (this.c_SoftwareListView.InvokeRequired)
                this.Invoke(lambda);
            else
                lambda();
        }

        /// <summary>
        /// Callback executed when the update of an application component has failed.
        /// </summary>
        /// <param name="sender">The sender of the event.</param>
        /// <param name="e">The event information.</param>
        private void OnUpdateFailed(object sender, Installation.InstallationEventArgs e)
        {
            RoketPack.Management.Application a = e.Installation.Application;
            ListViewItem.ListViewSubItem item = this.GetSubItemForApplication(a);
            if (item == null) return;

            Manager.VoidLambda lambda = () =>
            {
                item.ForeColor = Color.OrangeRed;
                item.Font = new Font(item.Font, FontStyle.Bold);
                item.Text = "Update Failed";
                e.Installation.Application.InstallationData = null;
                this.SetProductPage(e.Installation.Application);

                if (this.Visible == false)
                {
                    this.m_Manager.SystemIconShowBalloon(100, a.Name, "RoketPack was unable to update " + a.Name + " to the latest version.", ToolTipIcon.Info);
                }

                e.Installation.Animator.Stop();
            };

            if (this.c_SoftwareListView.InvokeRequired)
                this.Invoke(lambda);
            else
                lambda();
        }

        /// <summary>
        /// Callback executed when the uninstallation of an application component is
        /// successful.
        /// </summary>
        /// <param name="sender">The sender of the event.</param>
        /// <param name="e">The event information.</param>
        private void OnUninstallComplete(object sender, Installation.InstallationEventArgs e)
        {
            RoketPack.Management.Application a = e.Installation.Application;
            ListViewItem.ListViewSubItem item = this.GetSubItemForApplication(a);
            if (item == null) return;

            Manager.VoidLambda lambda = () =>
            {
                item.ForeColor = Color.Black;
                item.Font = new Font(item.Font, FontStyle.Regular);
                this.UpdateListItem(a, item, e.Installation.Mode);
                e.Installation.Application.InstallationData = null;
                this.SetProductPage(e.Installation.Application);

                if (this.Visible == false)
                {
                    this.m_Manager.SystemIconShowBalloon(100, a.Name, "Uninstallation of " + a.Name + " is complete.", ToolTipIcon.Info);
                }

                e.Installation.Animator.Stop();
            };

            if (this.c_SoftwareListView.InvokeRequired)
                this.Invoke(lambda);
            else
                lambda();
        }

        /// <summary>
        /// Callback executed when the uninstallation of an application component
        /// progresses further.
        /// </summary>
        /// <param name="sender">The sender of the event.</param>
        /// <param name="e">The event information.</param>
        private void OnUninstallProgress(object sender, Installation.InstallationEventArgs e)
        {
            RoketPack.Management.Application a = e.Installation.Application;
            ListViewItem.ListViewSubItem item = this.GetSubItemForApplication(a);
            if (item == null) return;

            Manager.VoidLambda lambda = () =>
            {
                item.ForeColor = Color.ForestGreen;
                item.Font = new Font(item.Font, FontStyle.Bold);
                if (e.Installation.Stage == Installation.StageMode.DOWNLOAD)
                    item.Text = "Downloading... " + e.Installation.Progress + "%"; // Theoretically this should never be shown..
                else
                    item.Text = "Uninstalling... (file " + e.Installation.ProcessedFiles + " of " + e.Installation.TotalFiles + ")";
            };

            if (this.c_SoftwareListView.InvokeRequired)
                this.Invoke(lambda);
            else
                lambda();
        }

        /// <summary>
        /// Callback executed when the uninstallation of an application component
        /// has failed.
        /// </summary>
        /// <param name="sender">The sender of the event.</param>
        /// <param name="e">The event information.</param>
        private void OnUninstallFailed(object sender, Installation.InstallationEventArgs e)
        {
            RoketPack.Management.Application a = e.Installation.Application;
            ListViewItem.ListViewSubItem item = this.GetSubItemForApplication(a);
            if (item == null) return;

            Manager.VoidLambda lambda = () =>
            {
                item.ForeColor = Color.OrangeRed;
                item.Font = new Font(item.Font, FontStyle.Bold);
                item.Text = "Uninstallation Failed";
                e.Installation.Application.InstallationData = null;
                this.SetProductPage(e.Installation.Application);

                if (this.Visible == false)
                {
                    this.m_Manager.SystemIconShowBalloon(100, a.Name, "RoketPack was unable to uninstall " + a.Name + ".", ToolTipIcon.Error);
                }

                e.Installation.Animator.Stop();
            };

            if (this.c_SoftwareListView.InvokeRequired)
                this.Invoke(lambda);
            else
                lambda();
        }

        private delegate ListViewItem.ListViewSubItem GetSubItemForApplicationLambda();
        private ListViewItem.ListViewSubItem GetSubItemForApplication(RoketPack.Management.Application application)
        {
            ListViewItem.ListViewSubItem itm = null;
            GetSubItemForApplicationLambda getitems = () =>
            {
                foreach (ListViewItem item in this.c_SoftwareListView.Items)
                {
                    if (item.Tag is ListViewData)
                    {
                        ListViewData data = (ListViewData)item.Tag;
                        if (data.Application == application && item.SubItems.Count == 2)
                        {
                            return item.SubItems[1];
                        }
                    }
                }

                return null;
            };

            if (this.c_SoftwareListView.InvokeRequired)
                itm = (ListViewItem.ListViewSubItem)this.Invoke(getitems);
            else
                itm = getitems();

            return itm;
        }

        /// <summary>
        /// When we are closing the form, we really want to actually just hide it if
        /// there's an installation or update in progress.  The only case where we should
        /// actually close the form is when a computer shutdown or logout is in process.
        /// </summary>
        private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (e.CloseReason == CloseReason.UserClosing)
            {
                e.Cancel = true;
                this.Hide();
            }
        }

        private void c_RefreshInformationToolStripButton_Click(object sender, EventArgs e)
        {
            // Disable all of the on-screen component and show a message
            // indicating a refresh is in progress.
            this.SetRepositoryRefreshPage();
            this.c_SoftwareListView.Items.Clear();
            this.c_SoftwareListView.Groups.Clear();
            this.c_SoftwareListView.Groups.Add(new ListViewGroup("reporefresh", "Please Wait..."));
            this.c_SoftwareListView.Items.Add(
                new ListViewItem(
                    "Repository refresh is in progress...",
                    "roketpack",
                    this.c_SoftwareListView.Groups["reporefresh"]
                    )
                    );
            this.c_SoftwareListView.Enabled = false;

            // Refresh the repositories.
            this.m_Manager.RefreshRepositories();
        }

        private void m_Manager_RepositoriesRefreshed(object sender, EventArgs e)
        {
            // Re-enable everything again.
            RoketPack.Manager.VoidLambda lambda = () =>
                {
                    this.c_SoftwareListView.Enabled = true;
                    this.SetDefaultPage();
                };

            if (this.c_SoftwareListView.InvokeRequired)
                this.c_SoftwareListView.Invoke(lambda);
            else
                lambda();
        }

        private void c_SettingsToolStripButton_Click(object sender, EventArgs e)
        {
            this.m_Manager.OpenSettings(this, e);
        }

        /// <summary>
        /// This function shows the "repository initialization in progress" screen.
        /// It is used when the Manager has been instructed to reinitalize the
        /// repositories and needs to represent the situation on the main form.
        /// </summary>
        internal void ShowRepositoryReinitalization()
        {
            // Disable all of the on-screen component and show a message
            // indicating a initialization is in progress.
            this.SetRepositoryInitializationPage();
            this.c_SoftwareListView.Items.Clear();
            this.c_SoftwareListView.Groups.Clear();
            this.c_SoftwareListView.Groups.Add(new ListViewGroup("reporefresh", "Please Wait..."));
            this.c_SoftwareListView.Items.Add(
                new ListViewItem(
                    "Repository initialization is in progress...",
                    "roketpack",
                    this.c_SoftwareListView.Groups["reporefresh"]
                    )
                    );
            this.c_SoftwareListView.Enabled = false;
        }
    }

    internal class ListViewData
    {
        internal Repository Repository;
        internal RoketPack.Management.Application Application;

        internal ListViewData(Repository repository, RoketPack.Management.Application application)
        {
            this.Repository = repository;
            this.Application = application;
        }
    }
}
